﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using XcoAppSpaces.Contracts.Logging;
using XcoAppSpaces.Core.Ports;

namespace XcoAppSpaces.Core.Communication
{
	class RemotePortRegistry : IDisposable
	{
		private const int DefaultLeaseTimeSeconds = 60;

		class RemotePortRegistration
		{
			public int LeaseTimeSeconds { get; set; }
			public DateTime LastActiveDate { get; set; }
			public IPort Port { get; set; }

			public DateTime ExpirationDate { get { return this.LastActiveDate.AddSeconds(this.LeaseTimeSeconds); } }
		}

		private readonly IXcoLogger log = XcoLoggerFactory.CreateInstance(typeof(RemotePortRegistry));

		private readonly Dictionary<Guid, RemotePortRegistration> strongPorts = new Dictionary<Guid, RemotePortRegistration>();
		private readonly Dictionary<Guid, WeakReference> weakPorts = new Dictionary<Guid, WeakReference>(); //registered ports are stored as weak references so they can be garbage collected when the overlying application has no more reference to them

		private const int PortRegistryCheckInterval = 20000; //interval for port check (currently every 20 seconds)
		private readonly Timer portRegistryCheckTimer;

		public int LeaseTimeSeconds { get; set; }

		public int Count { get { return strongPorts.Count + weakPorts.Count; } }

		public RemotePortRegistry()
		{
			this.LeaseTimeSeconds = DefaultLeaseTimeSeconds;
			portRegistryCheckTimer = new Timer(CheckPortExpiration, PortRegistryCheckInterval);
		}
		
		public Guid RegisterPermanently(IPort port, Guid id)
		{
			lock (strongPorts)
			{
				if (!strongPorts.ContainsKey(id))
					strongPorts.Add(id, new RemotePortRegistration{Port = port, LeaseTimeSeconds = -1, LastActiveDate = DateTime.Now}); //-1 = never expires
			}
			return id;
		}

		public Guid Register(IPort port)
		{
			lock (strongPorts)
			{
				var id = GetIdFromStrongPorts(port);
				if (id != null)
				{
					strongPorts[id.Value].LastActiveDate = DateTime.Now;
					return id.Value;
				}
				lock (weakPorts)
				{
					id = GetIdFromWeakPorts(port);
					if (id != null && MoveFromWeakToStrongPorts(id.Value))
						return id.Value;
				}

				//port is not yet registered
				Guid newId = Guid.NewGuid();
				strongPorts.Add(newId, new RemotePortRegistration { Port = port, LeaseTimeSeconds = this.LeaseTimeSeconds, LastActiveDate = DateTime.Now });
				log.Debug("Port registered with Guid: " + newId);
				return newId;
			}
		}

		public IPort Resolve(Guid id)
		{
			lock (strongPorts)
			{
				if (strongPorts.ContainsKey(id))
				{
					strongPorts[id].LastActiveDate = DateTime.Now;
					return strongPorts[id].Port;
				}
				lock (weakPorts)
				{
					if (weakPorts.ContainsKey(id))
					{
						var port = weakPorts[id].Target as IPort;
						if (port != null && MoveFromWeakToStrongPorts(id))
							return port;

						weakPorts.Remove(id);
						log.Warn("Tried to resolve port that has already been garbage collected. ID: " + id);
						return null;
					}
				}
			}
			return null;
		}

		private Guid? GetIdFromWeakPorts(IPort port)
		{
			foreach (var reg in weakPorts)
			{
				if (reg.Value.Target == port)
					return reg.Key;
			}
			return null;
		}

		private Guid? GetIdFromStrongPorts(IPort port)
		{
			foreach (var reg in strongPorts)
			{
				if (reg.Value.Port == port)
					return reg.Key;
			}
			return null;
		}

		private bool MoveFromWeakToStrongPorts(Guid id)
		{
			var port = weakPorts[id].Target as IPort;
			weakPorts.Remove(id);
			if (port == null)
				return false;
			strongPorts.Add(id, new RemotePortRegistration { Port = port, LeaseTimeSeconds = this.LeaseTimeSeconds, LastActiveDate = DateTime.Now });
			return true;
		}

		internal void CheckPortExpiration()
		{
			RemoveGarbageCollectedWeakPorts();
			MakeExpiredStrongPortsToWeakPorts();
		}

		private void RemoveGarbageCollectedWeakPorts()
		{
			lock (weakPorts)
			{
				var garbage = weakPorts.Where(p => !p.Value.IsAlive).Select(p => p.Key).ToList();
				foreach (var id in garbage)
				{
					weakPorts.Remove(id);
					log.Debug("Automatically unregistered port because it has already been garbage collected: " + id);
				}
			}
		}

		private void MakeExpiredStrongPortsToWeakPorts()
		{
			var now = DateTime.Now;
			
			lock (strongPorts)
			{
				var expired = strongPorts.Where(p => p.Value.LeaseTimeSeconds >= 0 &&  p.Value.ExpirationDate < now).ToList();
				lock (weakPorts)
				{
					foreach (var p in expired)
					{
						strongPorts.Remove(p.Key);
						weakPorts.Add(p.Key, new WeakReference(p.Value.Port));
						log.Debug("Registration for port expired: " + p.Key);
					}
				}
			}
		}

		

		#region IDisposable Members

		public void Dispose()
		{
			portRegistryCheckTimer.Dispose();
		}

		#endregion
	}
}
